package com.dake.jdk8.util;

import com.dake.jdk8.vo.reflect.Student;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 反射工具类
 *
 * @author fangqi
 * @date 2021-1-18 15:59:28
 */
public class ReflectionUtils {

    private static final String SET = "set";

    public static void main(String[] args) {
        String name = getValue(new Student(), "name");
        System.out.println(name);
    }

    /**
     * 使用反射方法，通过类的全限定名获取实例化对象。<br>
     * 其中{@code clazz.newInstance()}方法已在jdk9中被放弃。
     *
     * @param classFullName 类的全限定名
     * @param <T> 返回值类型
     * @return 实例化对象
     */
    public static <T> T getInstance(String classFullName) {
        try {
            Class<?> clazz = Class.forName(classFullName);
            return (T) clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException
                | InvocationTargetException e) {
            throw new RuntimeException("利用反射通过类的全限定名获取实例化对象失败：", e);
        }

    }

    /**
     * 通过反射给对象赋值.
     * <p>
     * <strong>注意：</strong><br>
     * 最常出错的地方是字段{@code fieldName}在对象{@code obj}的参数类型{@code obj.getClass().getDeclaredField(fieldName)
     * .getType()}和最终赋值的参数类型{@code fieldValue.getClass()}不一致。
     *
     * @param obj        实例化对象
     * @param fieldName  字段名称，首字母必须小写
     * @param fieldValue 参数值
     */
    public static void setValue2Obj(Object obj, String fieldName, Object fieldValue) {
        Assert.notNull(obj, "实例化对象不能为空");
        Assert.notNull(fieldName, "字段名称不能为空");
        Assert.notNull(obj, "参数值不能为空");

        try {
            // 获取该类的所有声明字段，不包括父类的
            Field field = obj.getClass().getDeclaredField(fieldName);
            // 禁用Java语言访问检查，提升反射速度
            field.setAccessible(true);
            // 给对象赋值
            field.set(obj, fieldValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("通过反射给对象赋值失败：", e);
        }
    }

    /**
     * 通过api的方法给对象赋值。
     *
     * @param obj
     * @param fieldName
     * @param fieldValue
     */
    public static void setValue2ObjByApi(Object obj, String fieldName, Object fieldValue) {
        try {
            PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, obj.getClass());
            Method writeMethod = propertyDescriptor.getWriteMethod();
            writeMethod.invoke(obj, fieldValue);
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("通过反射给对象赋值失败：", e);
        }
    }

    /**
     * 通过反射的set方法给对象赋值。
     *
     * @param obj 实例化对象
     * @param methodName 方法名称
     * @param paramValue 参数取值
     * @param
     */
    public static void invokeMethod(Object obj, String methodName, Object paramValue, Class<?>... paramClazz) {
        if (ObjectUtils.isEmpty(obj) || StringUtils.isBlank(methodName)) {
            throw new RuntimeException("对象【{}】或方法名【{}】不能为空");
        }

        try {
            Method declaredMethod = obj.getClass().getDeclaredMethod(methodName, paramClazz);
            declaredMethod.invoke(obj, paramValue);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("通过反射的set方法给对象赋值失败：", e);
        }
    }

    /**
     * 通过反射获取实例化对象的指定字段的值。
     *
     * @param obj 实例化对象
     * @param fieldName 字段名称
     * @param <T> 返回值类型
     * @return 字段的返回值
     */
    @SuppressWarnings("unchecked")
    public static <T> T getValue(Object obj, String fieldName) {
        try {
            Field declaredField = obj.getClass().getDeclaredField(fieldName);
            declaredField.setAccessible(true);
            return (T) declaredField.get(obj);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("通过反射获取字段的值失败：", e);
        }
    }

    public static <T> void setValue2Object(Object object, String filed, T value) {
        try {
            Field declaredField = object.getClass().getDeclaredField(filed);
            declaredField.setAccessible(true);
//            Class<?> type = declaredField.getType();
//            String name = type.getName();
//            if (name instanceof T){
//
//            }

//            BeanUtils.

            PropertyDescriptor propertyDescriptor = new PropertyDescriptor(filed, object.getClass());
            Method writeMethod = propertyDescriptor.getWriteMethod();
            writeMethod.setAccessible(true);
            writeMethod.invoke(object, value);

            declaredField.set(object, value);
        } catch (IllegalAccessException | IntrospectionException | InvocationTargetException | NoSuchFieldException e) {
            throw new RuntimeException("通过反射给对象赋值失败");
        }
    }

/*    public static void getValue(Object object, String filed) {
        try {
            Field declaredField = object.getClass().getDeclaredField(filed);
            String name = declaredField.getName();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }*/

    public static Map<String, Object> object2Map(Object object) {
        Map<String, Object> map = new LinkedHashMap<>();
        Method[] methods = object.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("get")) {
                String field = method.getName();
                field = field.substring(field.indexOf("get") + 3);
                field = field.toLowerCase().charAt(0) + field.substring(1);

                Object value = null;
                try {
                    value = method.invoke(object, null);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }

                map.put(field, value);
            }
        }
        return map;
    }
}
